﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Windows.Forms;
using EzDeploy.Core;
using EzDeploy.Core.Controls;
using EzDeploy.Resources;

namespace EzDeploy
{
    public partial class MainForm : Form, IActionHost, IDisplayNotification, ITaskStateUIHint
    {
        Project _project;
        private Config _config;

        readonly IProjectService _projectService;
        readonly IAssemblyFilterProcessor _assemblyFilterProcessor;
        private readonly IResolveAssemblyReferenceTask _resolveAssemblyTask;

        readonly ICommandCheck _duplicateAssemblyNameCheck;
        readonly IDeployGridItemBinder _deployItemBinder;
        readonly IDirectoryBrowser _directoryBrowser;

        private Dictionary<string, IAction> _modules; 

        string _lstSavedProjectFile;
        string _lstOpenedProjectFile;

        private int _registeredMenuCount;

        private Dictionary<string, IAction> Modules
        {
            get
            {
                if(_modules == null)
                    _modules = new Dictionary<string, IAction>();
                return _modules;
            }
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="MainForm"/> class.
        /// </summary>
        public MainForm()
        {
            this.InitializeComponent();

            _project = new Project();
            _project.Changed += ProjectChanged;

            _projectService = new ProjectService();
            _assemblyFilterProcessor = new AssemblyFilterProcessor();

            _resolveAssemblyTask = new ResolveAssemblyReferenceTask(new AssemblyReferenceResolver());
            _resolveAssemblyTask.Complete += ResolveAssemblyComplete;

            _directoryBrowser = new DirectoryBrowser();
            _duplicateAssemblyNameCheck = new DuplicateAssemblyNameCheck();
            _deployItemBinder = new DeployGridItemBinder(deployGridItemBindingSource);

            this.Register(new CopyAssemblyAction());
            this.Register(new ZipAssemblyAction());
            this.Register(new EmbedAssemblyAction());
        }

        private Config GetConfig()
        {
            if (_config == null)
                _config = ConfigHelpers.Load(Application.StartupPath);

            return _config;
        }

        private void FormLoad(object sender, EventArgs e)
        {
            if (!this.DesignMode)
            {
                projectProperty.SetLabelColumnWidth(162);
                deploymentTypeCell.DataSource = Enum.GetValues(typeof(DeploymentType));
                projectProperty.SelectedObject = _project;
                foreach (var excl in this.GetConfig().Excluded)
                    _project.Excluded.Add(excl);
            }
        }

        public void Register(IAction action)
        {
            this.Modules.Add(action.Name, action);

            action.Executing += ModuleTaskExecuting;
            action.Executed += ModuleTaskExecuted;

            var menuItem = new ToolStripMenuItem(action.Menu.Text, null, this.MenuAction) { ShortcutKeys = action.Menu.Shortcut, Tag = action.Name};
            menuAction.DropDownItems.Insert(_registeredMenuCount++, menuItem);
        }

        private void ModuleTaskExecuting(object sender, TaskExecutingEventArgs args)
        {
            if (!this.CanDoAction())
            {
                args.Cancel = true;
                return;
            }

            var module = (IAction) sender;

            switch (module.Name)
            {
                case "Copy Assembly":
                    if (!this.CheckForNoDuplicateAssemblyName(deployGridItemBindingSource.GetDeployGridItems()))
                        args.Cancel = true;
                    break;
                case "Embed Assembly":
                    this.PreEmbedAssembly(args);
                    break;
                case "Zip Assembly":
                    if (!this.CheckForNoDuplicateAssemblyName(deployGridItemBindingSource.GetDeployGridItems()))
                        args.Cancel = true;
                    break;
            }

            if(!args.Cancel)
                this.DisplayTaskRunningUIHint();
        }

        private void ModuleTaskExecuted(object sender, Result args)
        {
            this.DisplayTaskCompleteUIHint();
            switch (args.ResultType)
            {
                case ResultType.Canceled:
                    if (!string.IsNullOrEmpty(args.Message))
                        this.Display(args.Message, NotificationMessageType.Info);
                    break;
                default:
                    this.Display(args.Message, args.ResultType == ResultType.Success ? NotificationMessageType.Info : NotificationMessageType.Error);
                    break;
            }
        }

        private void MenuAction(object sender, EventArgs eventArgs)
        {
            var key = (string) ((ToolStripItem) sender).Tag;
            var module = _modules[key];

            module.Task.Run(_project, deployGridItemBindingSource.GetDeployGridItems());
        }

        private void ResolveAssemblyComplete(object sender, Result args)
        {
            this.DisplayTaskCompleteUIHint();

            if (args.ResultType == ResultType.Success)
            {
                _deployItemBinder.SetValue((List<DeployGridItem>) args.Value);

                this.IncludeOnlyValidItemWithPattern(this, EventArgs.Empty);
                this.ApplyProjectDeploymentType();
            }
            else 
                this.Display(args.Message, NotificationMessageType.Error);

            menuAction.Enabled = deployGridItemBindingSource.Count>0;
        }

        private void ProjectChanged(object sender, ProjectProperty property)
        {
            switch(property)
            {
                case ProjectProperty.HideExcludedAssembly:
                case ProjectProperty.Excluded:
                    this.IncludeOnlyValidItemWithPattern(sender, EventArgs.Empty);
                    break;
                case ProjectProperty.AssemblyFile:
                    this.ResolveAssemblyReference();
                    break;
            }
        }


        private void ResolveAssemblyReference()
        {
            deployGridItemBindingSource.Clear();
            this.DisplayTaskRunningUIHint();

            _resolveAssemblyTask.Resolve(_project);
        }

        private void ApplyProjectDeploymentType()
        {
            foreach (var item in _deployItemBinder.GetValue())
            {
                DeploymentType deploymentType;
                if (_project.Assemblies.TryGetValue(item.AssemblyName.FullName, out deploymentType))
                {
                    if (item.DeploymentType != deploymentType)
                        item.DeploymentType = deploymentType;
                }
            }
        }

        public void DisplayTaskRunningUIHint()
        {
            projectProperty.Enabled = false;
            dgAssemblies.Enabled = false;
            menuAction.Enabled = false;
            picProperty.Visible = false;
            lbAssemblyFullName.Visible = false;

            picLoading.Visible = true;
            txtLoading.Visible = true;
            lbAssemblyFullName.Text = string.Empty;
        }

        public void DisplayTaskCompleteUIHint()
        {
            projectProperty.Enabled = true;
            dgAssemblies.Enabled = true;
            lbAssemblyFullName.Visible = true;
            menuAction.Enabled = deployGridItemBindingSource.Count > 0;

            picLoading.Visible = false;
            txtLoading.Visible = false;
        }

        private void IncludeAll(object sender, EventArgs e)
        {
            deployGridItemBindingSource.Clear();

            _deployItemBinder.Reset();
            _deployItemBinder.Bind();

            foreach (DeployGridItem item in deployGridItemBindingSource.List)
            {
                if(item.ItemStatus != VersionStatus.Unknown)
                    item.DeploymentType = DeploymentType.Include;   
            }

            dgAssemblies.Refresh();
        }

        private void IncludeOnlyValidItem(object sender, EventArgs e)
        {
            deployGridItemBindingSource.Clear();

            _deployItemBinder.Reset();
            _deployItemBinder.Bind();

            foreach (DeployGridItem item in deployGridItemBindingSource.List)
                item.DeploymentType = item.ItemStatus == VersionStatus.Match ? DeploymentType.Include : DeploymentType.Exclude;
        }

        private void IncludeOnlyValidItemWithPattern(object sender, EventArgs e)
        {
            deployGridItemBindingSource.Clear();

            _deployItemBinder.Reset();
            _deployItemBinder.Bind(item=>
            {
                if (item.ItemStatus != VersionStatus.Unknown)
                {
                    _assemblyFilterProcessor.Filter(_project.Excluded, item);
                    if (item.DeploymentType == DeploymentType.Exclude)
                    {
                        if (item.ItemStatus == VersionStatus.Match && _project.HideExcludedAssembly) // Always display VersionMismatch file to user.
                            return false;
                        return true;
                    }

                    return true;
                }

                if (item.DeploymentType != DeploymentType.Exclude) // Always display FileNotFound file to user.
                    item.DeploymentType = DeploymentType.Exclude;
                return true;
            });
        }

        private void SaveProject(object sender, EventArgs e)
        {
            if (!string.IsNullOrEmpty(_lstSavedProjectFile))
            {
                saveProjectFileDialog.InitialDirectory = Path.GetDirectoryName(_lstSavedProjectFile);
                saveProjectFileDialog.FileName = _lstSavedProjectFile;
            }

            if (saveProjectFileDialog.ShowDialog() == DialogResult.OK)
            {
                _lstSavedProjectFile = saveProjectFileDialog.FileName;

                if (string.IsNullOrEmpty(_lstOpenedProjectFile))
                    _lstOpenedProjectFile = _lstSavedProjectFile;

                _projectService.Save(_project, deployGridItemBindingSource.GetDeployGridItems(), saveProjectFileDialog.FileName);

                this.Display(string.Format(MessageResource.ProjectSavedToFmt, _lstSavedProjectFile), NotificationMessageType.Info);
            }
        }

        private void OpenProjectFile(object sender, EventArgs e)
        {
            if (!string.IsNullOrEmpty(_lstOpenedProjectFile))
            {
                openProjectFileDialog.InitialDirectory = Path.GetDirectoryName(_lstOpenedProjectFile);
                openProjectFileDialog.FileName = _lstOpenedProjectFile;
            }

            if (openProjectFileDialog.ShowDialog() == DialogResult.OK)
            {
                _lstOpenedProjectFile = openProjectFileDialog.FileName;
                if (string.IsNullOrEmpty(_lstSavedProjectFile))
                    _lstSavedProjectFile = _lstOpenedProjectFile;

                this.LoadProject();
            }
        }

        private void LoadProject()
        {
            _project.Changed -= this.ProjectChanged;
            _project = _projectService.Load(_lstOpenedProjectFile);
            _project.Changed += this.ProjectChanged;

            projectProperty.SelectedObject = _project;

            this.ResolveAssemblyReference();
        }


        void PreEmbedAssembly(TaskExecutingEventArgs args)
        {
            if (Directory.Exists(_project.DeployedDirectory))
            {
                if (_project.AssemblyHasPublicKey)
                {
                    if (string.IsNullOrEmpty(_project.StrongnameKeyFile) && !File.Exists(_project.StrongnameKeyFile))
                    {
                        using (var frm = new StrongNameKeyWarningForm(_project))
                        {
                            var dlg = frm.ShowDialog();
                            if (dlg == DialogResult.Cancel)
                                args.Cancel = true;
                            if (dlg == DialogResult.OK)
                                _project.StrongnameKeyFile = frm.StrongNameKeyFile;
                        }
                    }
                }
            }
            else
            {
                args.Message = MessageResource.DeploymentDirectoryNotFound;
                args.Cancel = true;
            }
        }

        private bool CanDoAction()
        {
            return menuAction.Enabled;
        }

        private bool CheckForNoDuplicateAssemblyName(IEnumerable<DeployGridItem> items)
        {
            _duplicateAssemblyNameCheck.Reset();
            _duplicateAssemblyNameCheck.Check(items, _project);

            if (_duplicateAssemblyNameCheck.ErrorFound)
            {
                var dlg = _duplicateAssemblyNameCheck.ResultForm.ShowDialog();
                if (dlg == DialogResult.OK)
                    _duplicateAssemblyNameCheck.Fix(_project);
                if (dlg == DialogResult.Cancel)
                    return false;
            }

            return true;
        }

        private void BrowseAssemblyDirectory(object sender, EventArgs e)
        {
            var item = dgAssemblies.SelectedRows[0].DataBoundItem as DeployGridItem;
            if (item != null)
                _directoryBrowser.Browse(item.ItemFullPath);
        }

        private void BrowseAssemblySourceDirectory(object sender, EventArgs e)
        {
            _directoryBrowser.Browse(_project.AssemblyFile);
        }

        private void BrowseDeploymentDirectory(object sender, EventArgs e)
        {
            if (menuAction.Enabled)
                _directoryBrowser.Browse(_project.DeployedDirectory);
        }

        private void CopyAssemblyFullName(object sender, EventArgs e)
        {
            var item = dgAssemblies.SelectedRows[0].DataBoundItem as DeployGridItem;
            if (item != null)
                Clipboard.SetText(item.AssemblyName.FullName);
        }

        private void ShowLegendForm(object sender, EventArgs e)
        {
            using (var frm = new LegendForm())
                frm.ShowDialog();
        }

        private void ShowDetailForm(object sender, EventArgs e)
        {
            if (dgAssemblies.SelectedRows.Count > 0)
            {
                var item = dgAssemblies.SelectedRows[0].DataBoundItem as DeployGridItem;
                using (var frm = new DetailForm(item))
                    frm.ShowDialog();
            }
        }

        private void ShowDetailForm(object sender, DataGridViewCellEventArgs e)
        {
            if (e.ColumnIndex == 1)
                this.ShowDetailForm(sender, EventArgs.Empty);
        }

        private void ShowOptionForm(object sender, EventArgs e)
        {
            using (var frm = new OptionsForm())
            {
                if (frm.ShowDialog() == DialogResult.OK)
                {
                    _config = frm.Config;
                    if (_project.Excluded.Count == 0 && _config.Excluded.Count > 0)
                    {
                        foreach (var excl in _config.Excluded)
                            _project.Excluded.Add(excl);
                    }
                }
            }
        }

        private void ShowAboutForm(object sender, EventArgs e)
        {
            using (var frm = new AboutForm())
                frm.ShowDialog();
        }

        private void BrowseDocumentation(object sender, EventArgs e)
        {
            Process.Start("http://ezydeploy.codeplex.com/documentation");
        }

        private void CloseForm(object sender, EventArgs e)
        {
            this.Close();
        }

        private void DataGridViewSelectionChanged(object sender, EventArgs e)
        {
            if (dgAssemblies.SelectedRows.Count > 0)
            {
                var item = dgAssemblies.SelectedRows[0].DataBoundItem as DeployGridItem;
                if (item != null)
                {
                    lbAssemblyFullName.Text = item.AssemblyName.FullName;

                    if (!picProperty.Visible)
                        picProperty.Visible = true;
                }
            }
            else
            {
                if (picProperty.Visible)
                    picProperty.Visible = false;
            }
        }

        private void GridMenuOpening(object sender, CancelEventArgs e)
        {
            e.Cancel = dgAssemblies.RowCount == 0 && dgAssemblies.SelectedRows.Count == 0;

            if (!e.Cancel)
            {
                var item = dgAssemblies.SelectedRows[0].DataBoundItem as DeployGridItem;
                bool exists = item.ItemStatus != VersionStatus.Unknown;
                tsmBrowseAssemblyDirectory.Enabled = exists;
            }
        }

        private void OnDragEnter(object sender, DragEventArgs e)
        {
            var fileNames = e.Data.GetData("FileDrop") as string[];

            if (fileNames != null && fileNames.Length>0)
            {
                string fileExt = Path.GetExtension(fileNames[0]).ToLowerInvariant();

                switch(fileExt.ToLowerInvariant())
                {
                    case ".exe":
                    case ".dll":
                    case ".ezd":
                        e.Effect = DragDropEffects.Link;
                        return;
                }
            }

            e.Effect = DragDropEffects.None;
        }

        private void OnDragDrop(object sender, DragEventArgs e)
        {
            if (e.Effect == DragDropEffects.Link)
            {
                var fileNames = e.Data.GetData("FileDrop") as string[];
                if (fileNames != null && fileNames.Length>0)
                {
                    string fileExt = Path.GetExtension(fileNames[0]).ToLowerInvariant();

                    switch (fileExt.ToLowerInvariant())
                    {
                        case ".exe":
                        case ".dll":
                            _project.AssemblyFile = fileNames[0];
                            break;
                        case ".ezd":
                            _lstOpenedProjectFile = fileNames[0];
                            this.LoadProject();
                            break;
                    }
                }
            }
        }

        public void Display(string msg, NotificationMessageType msgType)
        {
            this.notifyIcon.ShowBalloonTip(8000, msgType == NotificationMessageType.Error ? MessageResource.ErrorText : MessageResource.InformationText, msg, msgType == NotificationMessageType.Error ? ToolTipIcon.Error : ToolTipIcon.None);
        }
    }
}
